package org.vnl.platform.job;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Stack;

import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
import org.dom4j.Document;
import org.dom4j.Node;
import org.vnl.platform.dk.util.Dom4jHelper;

/**
 * 利用XML設定執行一連串的Job
 * <pre>
 * 
<?xml version="1.0" encoding="UTF-8"?>
<JobEngine>
    <JobGroup name="env-setup" type="IGNORE"> 
        <Job class="">
            <Param name="FILE_LOCATION" value="/log4j.properties" />
        </Job>
        <Job class="">
            <Param name="PERSISTE_UNIT_NAME" value="" />
            <Param name="PERSISTENCE_MANAGER_NAME" value="" />
        </Job>
    </JobGroup>
</JobEngine>
其中//JobEngine/JobGroup@type請參考JobGroupType,
//JobEngine/JobGroup/Job@class中為Job的full class name
//JobEngine/JobGroup/Job/Param為會傳入Job作為initialize的參數
 * </pre>
 * 
 * @author Vaughan
 *
 */
public class JobExecuteEngine {
    
    private static final Logger logger = Logger.getLogger(JobExecuteEngine.class);
    
    private LinkedList<JobGroup> jobGroups;

    public JobExecuteEngine() {
        jobGroups = new LinkedList<JobExecuteEngine.JobGroup>();
        
    }
    
    /**
     * 設定Job Engine執行
     * 
     * @param xmlFileLocation
     * @throws JobExecException
     */
    public void init(String xmlFileLocation) throws JobExecException {
        Document doc = Dom4jHelper.getInstance().getDocument(xmlFileLocation);
        if (doc == null) {
            throw new JobExecException(JobErrCode.LOADING_JOB_SETTING_ERR, new Object[] {xmlFileLocation});
        }
        List<? extends Node> nodes = doc.selectNodes("//JobEngine/JobGroup");
        for (Node node : nodes) {
            JobGroup jg = new JobGroup(); 
            jg.setName(node.valueOf("@name"));
            String type = node.valueOf("@type");
            jg.setType(StringUtils.isBlank(type) ? JobGroupType.IGNORE : JobGroupType.valueOf(type));
            List<? extends Node> jNodes = node.selectNodes("Job");
            for (Node jNode : jNodes) {
                String clzName = jNode.valueOf("@class");
                if (StringUtils.isBlank(clzName)) {
                    logger.debug("Initialize Job Enginee Fail:"+xmlFileLocation);
                    throw new JobExecException(JobErrCode.INIT_JOB_ENGINE_ERR, new Object[] { xmlFileLocation });
                }
                try {
                    Object obj = Class.forName(clzName, true, Thread.currentThread().getContextClassLoader()).newInstance();
                    if (obj instanceof IJob) {
                        IJob job = (IJob) obj;
                        jg.addJob(job);
                        HashMap<String, Object> map = new HashMap<String, Object>();
                        List<? extends Node> pNodes = jNode.selectNodes("Param");
                        for (Node pNode : pNodes) {
                            String name = pNode.valueOf("@name");
                            String value = pNode.valueOf("@value");
                            Object o1 = map.get(name);
                            if (o1 == null) {
                                map.put(name, value);
                            } else {
                                if (o1 instanceof String) {
                                    ArrayList<String> list = new ArrayList<String>();
                                    list.add(o1.toString());
                                    list.add(value);
                                    map.put(name, list);
                                } else {
                                    if (o1 instanceof ArrayList) {
                                        @SuppressWarnings("unchecked")
                                        ArrayList<String> list = (ArrayList<String>) o1;
                                        list.add(value);
                                    }
                                }
                            }
                        }
                        job.setParams(map);
                    } else {
                        logger.debug("Inccorect IJob Type:"+clzName);
                        throw new JobExecException(JobErrCode.JOB_TYPE_ERR, new Object[] { clzName });
                    }
                } catch (IllegalAccessException e) {
                    logger.error("Initialize Job Enginee Exception!",e);
                    throw new JobExecException(JobErrCode.CREATE_JOB_ERR, new Object[] {clzName}, e);
                } catch (InstantiationException e) {
                    logger.error("Initialize Job Enginee Exception!",e);
                    throw new JobExecException(JobErrCode.CREATE_JOB_ERR, new Object[] {clzName}, e);
                } catch (ClassNotFoundException e) {
                    logger.error("Initialize Job Enginee Exception!",e);
                    throw new JobExecException(JobErrCode.CREATE_JOB_ERR, new Object[] {clzName}, e);
                }
            }
            this.jobGroups.add(jg);
        }
    }

    /**
     * 起動 Job Engine
     * 
     * @return Job Group name as key, result code as value(refer to IJob)
     * @throws JobExecException
     */
    public Map<String, Integer> startEngine() throws JobExecException {
        HashMap<String, Integer> result = new HashMap<String, Integer>();
        for (JobGroup jg : jobGroups) {
            logger.debug("Execute Job Group:"+jg.getName());
            Stack<IJob> jobs = new Stack<IJob>();
            int groupCode = IJob.RESULT_SUCCESS;
            for (IJob job : jg.getJobs()) {
                logger.debug("Execut Job:"+job.getClass().getName());
                int rCode = job.exec();
                if (rCode == IJob.RESULT_FAIL) {
                    logger.info("Execute Job FAIL : "+job.getClass().getName());
                    groupCode = rCode;
                    if (!jg.getType().equals(JobGroupType.IGNORE)) {
                        if (jg.getType().equals(JobGroupType.RESET_ON_FAIL)) {
                            while (!jobs.isEmpty()) {
                                jobs.pop().reset();
                            }
                        }
                        break;
                    }
                } else {
                    jobs.push(job);
                }
            }
            result.put(jg.getName(), groupCode);
        }
        return result;
    }
    
    class JobGroup {
        private String name;
        private JobGroupType type;
        private Collection<IJob> jobs;
        
        public JobGroup() {
            setType(JobGroupType.IGNORE);
            setJobs(new LinkedList<IJob>());
        }

        public JobGroupType getType() {
            return type;
        }

        public void setType(JobGroupType type) {
            this.type = type;
        }

        public Collection<IJob> getJobs() {
            return jobs;
        }

        public void setJobs(Collection<IJob> jobs) {
            this.jobs = jobs;
        }
        
        public void addJob(IJob job) {
            if (this.jobs == null) {
                this.jobs = new LinkedList<IJob>();
            }
            this.jobs.add(job);
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }
        
    }
    
}
